package com.dituhui.tool.algorithm;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.math3.ml.clustering.CentroidCluster;
import org.apache.commons.math3.ml.clustering.Cluster;
import org.apache.commons.math3.ml.clustering.Clusterable;
import org.apache.commons.math3.ml.clustering.Clusterer;
import org.apache.commons.math3.ml.clustering.DBSCANClusterer;
import org.apache.commons.math3.ml.clustering.DoublePoint;
import org.apache.commons.math3.ml.clustering.FuzzyKMeansClusterer;
import org.apache.commons.math3.ml.clustering.KMeansPlusPlusClusterer;
import org.apache.commons.math3.ml.distance.DistanceMeasure;
import org.apache.commons.math3.ml.distance.EuclideanDistance;

import com.dituhui.tool.util.StringUtil;

/**
 * 聚类分析工具
 * 
 * @author liweigu
 * 
 */
public class ClusterTool {
	/**
	 * 使用欧几里得距离公式进行距离据算
	 */
	private static DistanceMeasure Measure = new EuclideanDistance();

	/**
	 * 
	 * @Title: kMeansPlusPlusCluster
	 * @Description: 使用K均值算法进行聚类分析
	 * @param targets
	 * @param k 分组数
	 * @param maxIterations 迭代次数
	 * @return
	 * @author huanghuasong
	 */
	public static Map<Target, List<Target>> kMeansPlusPlusCluster(List<Clusterable> targets, int k, int maxIterations) {
		KMeansPlusPlusClusterer<Clusterable> kMeansPlusPlusClusterer = new KMeansPlusPlusClusterer<Clusterable>(k, maxIterations, Measure);
		return cluster(kMeansPlusPlusClusterer, targets);
	}

	/**
	 * 
	 * @Title: fuzzyKMeansCluster
	 * @Description: 使用模糊K均值算法，结果带有重叠
	 * @param targets
	 * @param fuzziness
	 * @param k 分组数
	 * @param maxIterations 迭代次数
	 * @return
	 * @author huanghuasong
	 */
	public static Map<Target, List<Target>> fuzzyKMeansCluster(List<Clusterable> targets, double fuzziness, int k, int maxIterations) {
		FuzzyKMeansClusterer<Clusterable> fuzzyKMeansClusterer = new FuzzyKMeansClusterer<Clusterable>(k, fuzziness, maxIterations, Measure);
		return cluster(fuzzyKMeansClusterer, targets);
	}

	/**
	 * 
	 * @Title: dbSCANCluster
	 * @Description: 使用密度算法进行聚类分析
	 * @param targets
	 * @param eps
	 * @param minPts
	 * @return
	 * @author huanghuasong
	 */
	public static Map<Target, List<Target>> dbSCANCluster(List<Clusterable> targets, double eps, int minPts) {
		DBSCANClusterer<Clusterable> dbSCANClusterer = new DBSCANClusterer<Clusterable>(eps, minPts, Measure);
		return cluster(dbSCANClusterer, targets);
	}

	private static Map<Target, List<Target>> cluster(Clusterer<Clusterable> clusterer, List<Clusterable> targets) {
		// 结果
		Map<Target, List<Target>> result = new HashMap<Target, List<Target>>();
		// 聚类分析
		List<Cluster<Clusterable>> clusterList = (List<Cluster<Clusterable>>) clusterer.cluster(targets);

		for (Cluster<Clusterable> item : clusterList) {
			Target center = new Target();
			// k均值才返回CentroidCluster,DBScan 算法不返回该对象
			if (item instanceof CentroidCluster) {
				DoublePoint doublePoint2 = (DoublePoint) ((CentroidCluster<Clusterable>) item).getCenter();
				double[] centerPoints = doublePoint2.getPoint();
				List<Double> point = new ArrayList<Double>();
				point.add(centerPoints[0]);
				point.add(centerPoints[1]);
				center.setPoint(point);
			}

			List<Target> resultTargets = new ArrayList<Target>();
			List<Clusterable> points = item.getPoints();
			for (Clusterable point : points) {
				Target target = (Target) point;
				resultTargets.add(target);
			}
			// // 排序。TODO: 排序可能影响性能，需要优化性能。
			// sortByOrder(resultTargets);
			result.put(center, resultTargets);
		}

		return result;
	}

	/**
	 * 获按order排序
	 * 
	 * @param originalList 待排序坐标
	 */
	private static void sortByOrder(List<Target> points) {
		// 按时间进行排序
		Collections.sort(points, new Comparator<Target>() {
			public int compare(Target arg0, Target arg1) {
				int result = 0;
				if (arg0.getProperties() != null && arg0.getProperties().size() > 1 && arg1.getProperties() != null && arg1.getProperties().size() > 1) {
					// order
					if (arg0.getProperties().get(1) == arg1.getProperties().get(1)) {
						result = 0;
					} else if (arg0.getProperties().get(1) > arg1.getProperties().get(1)) {
						result = 1;
					} else {
						result = -1;
					}
				}
				return result;
			}
		});
	}

	private static void printResult(Map<Target, List<Target>> result) {
		for (Target item : result.keySet()) {
			System.out.println("item" + item);
			List<Target> centroidTargets = result.get(item);
			System.out.println("size:" + centroidTargets.size());
			System.out.print("[");
			int count = 0;
			for (Target target : centroidTargets) {
				if (count > 0) {
					System.out.print(", ");
				}
				// order
				System.out.print(target.getProperties().get(1));
				count++;
			}
			System.out.println("]");
			System.out.println("----------------------------");
		}
	}

	// /**
	// * 根据Target的order属性进行删除
	// *
	// * @param gpsPoints 被删除的列表
	// * @param targetToRemove 要删除的Target对象
	// */
	// private static void removeByTargetOrder(List<Point> gpsPoints, Target
	// targetToRemove) {
	// int order = targetToRemove.getOrder();
	// // 遍历找索引
	// int index = -1;
	// for (Point gpsPoint : gpsPoints) {
	// index++;
	// if (gpsPoint.getOrder() == order) {
	// break;
	// }
	// }
	// // 如果找到了就删除
	// if (-1 != index) {
	// gpsPoints.remove(index);
	// }
	// }

	public static class Target implements Clusterable, Serializable {
		private static final long serialVersionUID = 1L;
		private List<Double> properties = new ArrayList<Double>();
		private List<Double> point = new ArrayList<Double>();

		/**
		 * 获取属性值
		 * 
		 * @return 属性值
		 */
		public List<Double> getProperties() {
			return this.properties;
		}

		/**
		 * 设置属性值
		 * 
		 * @param values 属性值
		 */
		public void setProperties(List<Double> properties) {
			this.properties = properties;
		}

		/**
		 * 设置点值
		 * 
		 * @param point 点值
		 */
		public void setPoint(List<Double> point) {
			this.point = point;
		}

		/**
		 * 获取点值
		 * 
		 * @return 点值
		 */
		@Override
		public double[] getPoint() {
			double[] point;
			if (this.properties != null && this.properties.size() > 1) {
				point = new double[this.properties.size()];
				for (int i = 0; i < this.properties.size(); i++) {
					point[i] = this.properties.get(i);
				}
			} else {
				point = new double[0];
			}
			return point;
		}

		@Override
		public String toString() {
			StringBuilder stringBuilder = new StringBuilder();
			// point
			if (this.point == null) {
				stringBuilder.append("[point=null]");
			} else if (this.point.size() == 0) {
				stringBuilder.append("[point=]");
			} else {
				stringBuilder.append("[point=");
				int i = 0;
				for (double value : this.point) {
					if (i++ > 0) {
						stringBuilder.append(StringUtil.COMMA);
					}
					stringBuilder.append(value);
				}
				stringBuilder.append("]");
			}
			// properties
			if (this.properties == null) {
				stringBuilder.append("[properties=null]");
			} else if (this.properties.size() == 0) {
				stringBuilder.append("[properties=]");
			} else {
				stringBuilder.append("[properties=");
				int i = 0;
				for (double value : this.properties) {
					if (i++ > 0) {
						stringBuilder.append(StringUtil.COMMA);
					}
					stringBuilder.append(value);
				}
				stringBuilder.append("]");
			}

			return stringBuilder.toString();
		}

	}
}
